package org.jdna.minecraft.tools;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.log4j.Logger;

public class ModInstallerDialog extends JDialog {
	private static final Logger log = Logger
			.getLogger(ModInstallerDialog.class);
	private static int MAX_TASKS = 6;

	private final JPanel contentPanel = new JPanel();
	private Map<String, Boolean> copied = new HashMap<String, Boolean>();

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		try {
			ModInstallerDialog dialog = new ModInstallerDialog();
			dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			dialog.setVisible(true);

			List<File> files = new ArrayList<File>();
			files.add(new File("/home/sean/MINECRAFT_MODS/ModLoader.zip"));
			files.add(new File("/home/sean/MINECRAFT_MODS/Arrows Mod.zip"));
			files.add(new File("/home/sean/MINECRAFT_MODS/Planes 1.7.3 v15.zip"));
			dialog.performUpdate(files, new IStatusHandler() {
				@Override
				public void installed(File f) {
					System.out.println("installed file: " + f);
				}

				@Override
				public void fail(File f, Throwable t) {
					System.out.println("Failed: " + f);
					t.printStackTrace();
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	Preferences prefs = Application.getInstance().getPreferences();

	JList list = new JList(new DefaultListModel());
	// JTextPane list = new JLi
	JButton btnOk = new JButton("Cancel");
	JLabel lblNewLabel = new JLabel("");
	JProgressBar progressBar = new JProgressBar();
	private final JScrollPane scrollPane = new JScrollPane();

	/**
	 * Create the dialog.
	 */
	public ModInstallerDialog() {
		setTitle("Installing Mods...");
		setBounds(100, 100, 640, 480);
		getContentPane().setLayout(new BorderLayout());
		contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
		getContentPane().add(contentPanel, BorderLayout.CENTER);
		GridBagLayout gbl_contentPanel = new GridBagLayout();
		gbl_contentPanel.columnWidths = new int[] { 0, 0 };
		gbl_contentPanel.rowHeights = new int[] { 0, 0, 0, 0, 0 };
		gbl_contentPanel.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
		gbl_contentPanel.rowWeights = new double[] { 0.0, 0.0, 1.0, 0.0,
				Double.MIN_VALUE };
		contentPanel.setLayout(gbl_contentPanel);
		GridBagConstraints gbc_lblNewLabel = new GridBagConstraints();
		gbc_lblNewLabel.fill = GridBagConstraints.HORIZONTAL;
		gbc_lblNewLabel.insets = new Insets(0, 0, 5, 0);
		gbc_lblNewLabel.gridx = 0;
		gbc_lblNewLabel.gridy = 0;
		contentPanel.add(lblNewLabel, gbc_lblNewLabel);

		GridBagConstraints gbc_progressBar = new GridBagConstraints();
		gbc_progressBar.fill = GridBagConstraints.HORIZONTAL;
		gbc_progressBar.insets = new Insets(0, 0, 5, 0);
		gbc_progressBar.gridx = 0;
		gbc_progressBar.gridy = 1;

		contentPanel.add(progressBar, gbc_progressBar);

		GridBagConstraints gbc_scrollPane = new GridBagConstraints();
		gbc_scrollPane.fill = GridBagConstraints.BOTH;
		gbc_scrollPane.insets = new Insets(0, 0, 5, 0);
		gbc_scrollPane.gridx = 0;
		gbc_scrollPane.gridy = 2;
		scrollPane
				.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		contentPanel.add(scrollPane, gbc_scrollPane);
		scrollPane.setViewportView(list);
		list.setAutoscrolls(true);
		list.setCellRenderer(new MyCellRenderer());

		GridBagConstraints gbc_btnOk = new GridBagConstraints();
		gbc_btnOk.gridx = 0;
		gbc_btnOk.gridy = 3;
		contentPanel.add(btnOk, gbc_btnOk);
		btnOk.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				close();
			}
		});
	}

	public void performUpdate(final List<File> files,
			final IStatusHandler handler) {
		SwingWorker t = new SwingWorker<Void, Void>() {
			@Override
			protected Void doInBackground() throws Exception {
				performUpdate2(files, handler);
				return null;
			}
		};
		t.execute();
	}

	public void performUpdate2(List<File> files, IStatusHandler handler) {
		progressBar.setIndeterminate(false);
		progressBar.setMinimum(0);
		// progressBar.setMaximum(MAX_TASKS);
		progressBar.setIndeterminate(true);

		lblNewLabel.setText("Installing files... Do not close the window.");
		
		try {
			int progress = 1;

			if (prefs.isCreateBackupsEnabled()) {
				if (prefs.isCreateFullBackupsEnabled()) {
					setProgress(progress++, "Creating full backup...");
					Application.getInstance().getBackupManager().fullBackup();
				} else {
					setProgress(progress++, "Creating backup of minecraft.jar...");
					Application.getInstance().getBackupManager().backupJarOnly();
				}
			}

			setProgress(progress++, "Preparing mods...");
			File dir = prefs.getMinecraftDirAsFile();
			if (!prefs.isValidMinecraftDir(dir)) {
				dir = Application.getInstance().chooseMinecraftDir();
				if (dir == null) {
					close();
					return;
				}

				prefs.setMinecraftDir(dir.getAbsolutePath());
			}

			if (!prefs.isValidMinecraftDir(dir)) {
				throw new Exception(
						"You must choose a valid minecraft installation directory");
			}

			setMessage("Extracting minecraft.jar...");
			log.info("extracting...");
			File tmpArea = prefs.getTmpArea();
			File mineDir = File.createTempFile("minecraft-", "-jar", tmpArea);
			if (mineDir.exists())
				mineDir.delete();
			mineDir.mkdirs();
			if (!mineDir.exists() || !mineDir.isDirectory()) {
				throw new Exception("Can't create directory: " + mineDir);
			}
			Util.unzip(prefs.getMinecraftJar(), mineDir);

			// remove the metainf dir
			File meta = new File(mineDir, "META-INF");
			if (meta.exists()) {
				setMessage("deleting META-INF");
				FileUtils.deleteDirectory(meta);
			}

			// extract and install mods
			for (File f : files) {
				try {
					if (!f.exists()) {
						throw new Exception("File Not Found: " + f);
					}
					setMessage("");
					setProgress(progress++, "Extracting " + f.getName());
					updateMinecraft(f, mineDir);
					handler.installed(f);
				} catch (Throwable t) {
					handler.fail(f, t);
				}
			}

			// create new minecraft.jar
			setProgress(progress++, "Creating new minecraft.jar");
			String name = BackupManager.createBackupName("minecraft.jar");
			File newjar = new File(prefs.getTmpArea(), name);
			Util.zip(mineDir, newjar);

			// overwrite the old minecraft.jar
			setProgress(progress++, "Installing new minecraft.jar");
			FileUtils.copyFile(newjar, prefs.getMinecraftJar());

			// copy the resources area
			File mockMineDir = new File(prefs.getTmpArea(), "mockminecraft");
			if (mockMineDir.exists()) {
				setProgress(progress++, "Copying Resources...");
				FileUtils.copyDirectory(mockMineDir, prefs.getMinecraftDirAsFile(),
						new TrackingFileFilter(mockMineDir, true), true);
			}
			// done
			setProgress(MAX_TASKS,
					"<html><b><font color='green'>Done</font></b></html>");
			lblNewLabel.setText("<html><b><font color='green'>Installing complete.  You can now close this window.</font></b></html>");
		} catch (Exception e) {
			lblNewLabel.setText("<html><b><font color=red>Installation Failed.  You should restore your Minecraft.jar</font></b></html>");
			log.error("Failed to install mods", e);
			Application.getInstance().error(
					"Error",
					"Install Failed: " + e.getMessage()
							+ "; See log for details.");
		} finally {
			btnOk.setText("Close");
			try {
				FileUtils.deleteDirectory(prefs.getTmpArea());
			} catch (IOException e) {
				setMessage("<html><font color='red'>Unable to clean out tmp area: " + prefs.getTmpArea() + "; You should manually delete this location.</font></html>");
				log.warn(
						"Could not clean up the tmp area: "
								+ prefs.getTmpArea(), e);
			}
			progressBar.setValue(0);
			progressBar.setIndeterminate(false);
		}
	}

	private class TrackingFileFilter implements FileFilter {
		private boolean verbose;
		private File base;

		public TrackingFileFilter(File baseDir, boolean verbose) {
			this.verbose = verbose;
			this.base = baseDir;
		}

		@Override
		public boolean accept(File pathname) {
			if (pathname.isFile()) {
				if (copied.containsKey(pathname.getAbsolutePath())) {
					setMessage("Collision: " + pathname);
				}
			}
			if (pathname.isDirectory()) {
				return true;
			}
			copied.put(pathname.getAbsolutePath(), Boolean.TRUE);
			progressBar.setValue(progressBar.getValue() + 1);
			if (verbose) {
				setMessage("<html>Copied: <i>"
						+ Util.relativePath(base, pathname) + "</i></html>");
			}
			return true;
		}
	}

	private void updateMinecraft(File f, File mineCraftJarDir)
			throws IOException {
		File zipDir = new File(prefs.getTmpArea(), f.getName());
		Util.unzip(f, zipDir);

		File mockMineDir = new File(prefs.getTmpArea(), "mockminecraft");
		FileUtils.forceMkdir(mockMineDir);
		File mockRes = new File(mockMineDir, "resources");
		FileUtils.forceMkdir(mockRes);

		// normal zip with .class files in the root
		if (hasClassFiles(zipDir)) {
			FileUtils.copyDirectory(zipDir, mineCraftJarDir,
					new TrackingFileFilter(prefs.getTmpArea(), true), true);
			return;
		}

		File files[] = zipDir.listFiles();
		if (files == null || files.length == 0) {
			setMessage(f.getName() + " contained no files");
			return;
		}

		for (File file : files) {
			if (file.isDirectory()) {
				// skip some files
				if (file.getName().startsWith("__"))
					continue;

				if (file.getName().toLowerCase().contains("resource")) {
					FileUtils.copyDirectory(file, mockRes,
							new TrackingFileFilter(prefs.getTmpArea(), true),
							true);
					continue;
				}

				File dir = findClassesDir(file);
				if (dir != null) {
					FileUtils.copyDirectory(dir, mineCraftJarDir,
							new TrackingFileFilter(prefs.getTmpArea(), true),
							true);
					continue;
				}

				setMessage("Unknown Directory " + file + " (ignored)");
			} else {
				if (!file.getName().toLowerCase().contains("readme")) {
					// copy std file to the minecraft root.
					setMessage("Copied: " + file.getName());
					FileUtils.copyFileToDirectory(file, mockMineDir);
					continue;
				}
			}
		}
	}

	private File findClassesDir(File dir) {
		Collection<File> files = FileUtils.listFiles(dir, new SuffixFileFilter(
				".class"), TrueFileFilter.TRUE);
		if (files != null && files.size() > 0) {
			return files.iterator().next().getParentFile();
		}
		return null;
	}

	private boolean hasClassFiles(File dir) {
		File[] files = dir.listFiles();
		if (files != null) {
			for (File f : files) {
				if (f.getName().endsWith(".class")) {
					return true;
				}
			}
		}
		return false;
	}

	protected void close() {
		setVisible(false);
	}

	private void setMessage(final String msg) {
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				((DefaultListModel) list.getModel()).addElement(msg);
				list.ensureIndexIsVisible(list.getModel().getSize() - 1);
			}
		});
	}

	public void setProgress(int progress) {
		progressBar.setValue(progress);
	}

	public void setProgress(int progress, String msg) {
		setProgress(progress);
		setMessage(msg);
	}

	public static class MyCellRenderer extends JLabel implements ListCellRenderer {
		public MyCellRenderer() {
			setOpaque(true);
		}

		public Component getListCellRendererComponent(JList list, Object value,
				int index, boolean isSelected, boolean cellHasFocus) {

			setText(value.toString());

			return this;
		}
	}
}
